#include "op-list.h"
#include "common.h"

InterCodes * first_op=0,*end_op=0;

static inline void insert_op(InterCode op){
    static int nr_num=0;
    InterCodes * now=malloc(sizeof(InterCodes));
    now->in=now->out=NULL;
    now->code=op;
    now->prev=end_op;now->next=NULL;
    now->run=0;now->block=NULL;
    now->nr_num=++nr_num;
    if(end_op) end_op->next=now;
    else first_op=now;
    end_op=now;
    if(op.kind==LABL||op.kind==FUNC){
        Operand x=op.x;
        x->entry=now;
    }
    return;
}

static inline char * Opstr(Operand x){
    if(!x) return NULL;
    switch(x->kind){
        case VARIABLE: return str_maker("a%u",x->u.var_no);
        case CONSTANT: return str_maker("#%u",x->u.value);
        case LABEL   : return str_maker("L%u",x->u.label);
        case NAME    : return (strcmp(x->u.name,"main")==0)?x->u.name:str_maker("f_%s",x->u.name);
        case CONST_F : return str_maker("#%f",x->u.f);
        default: Assert(0,"Unexpected type %d",x->kind);
    }
    Assert(0,"should not reach here!");return 0;
}

static inline char * relop_str(int x){
    switch(x){
        case GE: return ">=";
        case LE: return "<=";
        case GT: return ">";
        case LT: return "<";
        case EQ: return "==";
        case NE: return "!=";
        default: Assert(0,"Unexpected type %d",x);
    }
    Assert(0,"should not reach here!");return 0;
}

void output_op(FILE * output){
    int count=0;
    for(InterCodes * now=first_op;now;now=now->next,count++){
        InterCode op=now->code;
        char * x=Opstr(op.x), * y=Opstr(op.y), * z=Opstr(op.z);
        unsigned info=op.info;
        switch (op.kind){
            case LABL   : fprintf(output,"LABEL %s : \n",x); break;
            case FUNC   : fprintf(output,"FUNCTION %s : \n",x); break;
            case ASSIGN : fprintf(output,"%s := %s \n",x,y); break;
            case ADD    : fprintf(output,"%s := %s + %s \n",x,y,z); break;
            case SUB    : fprintf(output,"%s := %s - %s \n",x,y,z); break;
            case MUL    : fprintf(output,"%s := %s * %s \n",x,y,z); break;
            case OP_DIV : fprintf(output,"%s := %s / %s \n",x,y,z); break;
            case GTADDR : fprintf(output,"%s := &%s \n",x,y); break;
            case TOADDR : fprintf(output,"%s := *%s \n",x,y); break;
            case ASSADDR: fprintf(output,"*%s := %s \n",x,y); break;
            case GOTO   : fprintf(output,"GOTO %s \n",x); break;
            case OP_IF  : fprintf(output,"IF %s %s %s GOTO %s \n",x,relop_str(info),y,z); break;
            case RET    : fprintf(output,"RETURN %s \n",x); break;
            case DEC    : fprintf(output,"DEC %s %u \n",x,info); break;
            case ARG    : fprintf(output,"ARG %s \n",x); break;
            case CALL   : fprintf(output,"%s := CALL %s \n",x,y); break;
            case PARAM  : fprintf(output,"PARAM %s \n",x); break;
            case READ   : fprintf(output,"READ %s \n",x); break;
            case WRITE  : fprintf(output,"WRITE %s \n",x); break;
            case EXTRA_INST:
                if(op.nx) fprintf(output,"*");
                fprintf(output,"%s := ",x);
                if(info==CALL) fprintf(output,"CALL %s\n",y);
                else{
                    if(op.ny==1) fprintf(output,"*");
                    else if(op.ny==2) fprintf(output,"&");
                    fprintf(output,"%s ",y);
                    switch (info) {
                        case ADD: fprintf(output,"+ ");break;
                        case SUB: fprintf(output,"- ");break;
                        case MUL: fprintf(output,"* ");break;
                        case OP_DIV: fprintf(output,"/ ");break;
                    }
                    if(info!=ASSIGN) {
                        if(op.nz==1) fprintf(output,"*");
                        else if(op.nz==2) fprintf(output,"&");
                        fprintf(output,"%s ",z);
                    }
                    fprintf(output,"\n");
                }
                break;
            default: Assert(0,"Unexpected operad %d",op.kind);
        }
        if(x&&strcmp(x,"main")!=0) free(x);if(y) free(y);if(z) free(z);
    }
}

void op_pb(int kind,Operand x,Operand y,Operand z,int info){
    InterCode op;
    op.kind=kind;op.x=x;op.y=y;op.z=z;op.info=info;
    op.nx=op.ny=op.nz=0;
    insert_op(op);
}
void op_pb2(int type,Operand x,Operand y,Operand z,int nx,int ny,int nz){
    InterCode op;
    op.kind=EXTRA_INST;op.x=x;op.y=y;op.z=z;op.info=type;
    op.nx=nx;op.ny=ny;op.nz=nz;
    insert_op(op);
}

Operand wrap_op1(unsigned x,int kind){
    Operand a=malloc(sizeof(struct Operand_));
    a->kind=kind;a->offset=-1;a->entry=NULL;a->pos=0;
    switch(kind){
        case VARIABLE: a->u.var_no=x;break;
        case CONSTANT: a->u.var_no=x;break;
        case LABEL   : a->u.label=x;a->offset=0;break;
        default: Assert(0,"Unexpected operad %d",kind);
    }
    return a;
}

Operand wrap_op2(char * info,int kind){
    Operand a=malloc(sizeof(struct Operand_));
    a->kind=kind;a->offset=-1;a->entry=NULL;a->pos=0;
    switch(kind){
        case NAME    : a->u.name=info;break;
        default: Assert(0,"Unexpected operad %d",kind);
    }
    return a;
}

Operand wrap_op3(float f,int kind){
    Operand a=malloc(sizeof(struct Operand_));
    a->kind=kind;a->offset=-1;a->entry=NULL;a->pos=0;
    switch(kind){
        case CONST_F : a->u.f=f;break;
        default: Assert(0,"Unexpected operad %d",kind);
    }
    return a;
}